package host.springboot.framework.autoconfigure.web.jackson;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.*;
import com.fasterxml.jackson.datatype.jsr310.ser.*;
import host.springboot.framework.autoconfigure.web.KsWebAutoConfiguration;
import host.springboot.framework.context.mvc.jackson.deserialization.CustomOffsetDateTimeDeserializer;
import host.springboot.framework.context.mvc.jackson.deserialization.CustomOffsetTimeDeserializer;
import host.springboot.framework.context.mvc.jackson.deserialization.CustomZonedDateTimeDeserializer;
import host.springboot.framework.context.mvc.jackson.serialization.BaseEnumSerializer;
import host.springboot.framework.context.mvc.jackson.serialization.CustomOffsetDateTimeSerializer;
import host.springboot.framework.context.mvc.jackson.serialization.CustomOffsetTimeSerializer;
import host.springboot.framework.core.constant.PatternConstant;
import host.springboot.framework.core.enumeration.BaseEnum;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.boot.autoconfigure.jackson.JacksonProperties;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.util.ClassUtils;

import java.math.BigInteger;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Objects;
import java.util.TimeZone;

/**
 * Jackson自动配置
 *
 * <p>该类自动配置了 Jackson 的常用功能
 * 已默认配置序列化/反序列化规则如下:
 * <ul>
 *     <li>{@link BigInteger} auto mapping {@link String}</li>
 *     <li>{@link Long} auto mapping {@link String}</li>
 *     <li>{@link Enum} auto mapping {@link String}</li>
 *     <li>{@link BaseEnum} auto mapping {@link BaseEnum#getValue()}</li>
 *     <li>{@link Date} auto mapping if not configured {@code yyyy-MM-dd HH:mm:ss}</li>
 *     <li>{@link LocalDateTime} auto mapping if not configured {@code yyyy-MM-dd HH:mm:ss}</li>
 *     <li>{@link LocalDate} auto mapping {@code yyyy-MM-dd}</li>
 *     <li>{@link LocalTime} auto mapping {@code HH:mm:ss}</li>
 *     <li>{@link Year} auto mapping {@code yyyy}</li>
 *     <li>{@link YearMonth} auto mapping {@code yyyy-MM}</li>
 *     <li>{@link MonthDay} auto mapping {@code MM-dd}</li>
 *     <li>{@link ZonedDateTime} auto mapping {@code yyyy-MM-dd HH:mm:ss+HH:MM:ss[ZoneRegionId()]}</li>
 *     <li>{@link OffsetDateTime} auto mapping {@code yyyy-MM-dd HH:mm:ss+HH:MM:ss}</li>
 *     <li>{@link OffsetTime} auto mapping {@code HH:mm:ss+HH:MM:ss}</li>
 * </ul>
 * <p>该类采用 {@link Jackson2ObjectMapperBuilderCustomizer} 的原因是默认 Jackson 已存在一些默认配置内容,
 * 通过此种方式可以实现在不修改默认配置的情况下新增我们自定义的配置
 *
 * @author JiYinchuan
 * @see KsWebAutoConfiguration
 * @since 1.0.0
 */
public class JacksonObjectMapperBuilder implements Jackson2ObjectMapperBuilderCustomizer {

    /**
     * 默认时区
     */
    private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getTimeZone("Asia/Shanghai");

    /**
     * Jackson配置文件
     */
    private final JacksonProperties jacksonProperties;

    /**
     * 构造器
     *
     * @param jacksonProperties Jackson配置文件
     * @since 1.0.0
     */
    public JacksonObjectMapperBuilder(ObjectProvider<JacksonProperties> jacksonProperties) {
        this.jacksonProperties = jacksonProperties.getIfAvailable();
    }

    @Override
    public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
        jacksonObjectMapperBuilder.featuresToEnable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
        jacksonObjectMapperBuilder.serializerByType(BigInteger.class, ToStringSerializer.instance);
        jacksonObjectMapperBuilder.serializerByType(Long.class, ToStringSerializer.instance);
        jacksonObjectMapperBuilder.serializerByType(Enum.class, ToStringSerializer.instance);
        jacksonObjectMapperBuilder.serializerByType(BaseEnum.class, new BaseEnumSerializer());

        jacksonObjectMapperBuilder.serializerByType(LocalTime.class,
                new LocalTimeSerializer(PatternConstant.Date.NORM_TIME_FORMATTER));
        jacksonObjectMapperBuilder.serializerByType(LocalDate.class,
                new LocalDateSerializer(PatternConstant.Date.NORM_DATE_FORMATTER));
        jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class,
                new LocalDateTimeSerializer(PatternConstant.Date.NORM_DATETIME_FORMATTER));
        jacksonObjectMapperBuilder.serializerByType(Year.class,
                new YearSerializer(PatternConstant.Date.NORM_YEAR_FORMATTER));
        jacksonObjectMapperBuilder.serializerByType(YearMonth.class,
                new YearMonthSerializer(PatternConstant.Date.NORM_YEARMONTH_FORMATTER));
        jacksonObjectMapperBuilder.serializerByType(MonthDay.class,
                new MonthDaySerializer(PatternConstant.Date.NORM_MONTHDAY_FORMATTER));
        jacksonObjectMapperBuilder.serializerByType(ZonedDateTime.class,
                new ZonedDateTimeSerializer(PatternConstant.Date.NORM_ZONED_DATETIME_FORMATTER));
        jacksonObjectMapperBuilder.serializerByType(OffsetDateTime.class,
                new CustomOffsetDateTimeSerializer(PatternConstant.Date.NORM_OFFSET_DATETIME_FORMATTER));
        jacksonObjectMapperBuilder.serializerByType(OffsetTime.class,
                new CustomOffsetTimeSerializer(PatternConstant.Date.NORM_OFFSET_TIME_FORMATTER));

        jacksonObjectMapperBuilder.deserializerByType(LocalTime.class,
                new LocalTimeDeserializer(PatternConstant.Date.NORM_TIME_FORMATTER));
        jacksonObjectMapperBuilder.deserializerByType(LocalDate.class,
                new LocalDateDeserializer(PatternConstant.Date.NORM_DATE_FORMATTER));
        jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class,
                new LocalDateTimeDeserializer(PatternConstant.Date.NORM_DATETIME_FORMATTER));
        jacksonObjectMapperBuilder.deserializerByType(Year.class,
                new YearDeserializer(PatternConstant.Date.NORM_YEAR_FORMATTER));
        jacksonObjectMapperBuilder.deserializerByType(YearMonth.class,
                new YearMonthDeserializer(PatternConstant.Date.NORM_YEARMONTH_FORMATTER));
        jacksonObjectMapperBuilder.deserializerByType(MonthDay.class,
                new MonthDayDeserializer(PatternConstant.Date.NORM_MONTHDAY_FORMATTER));
        jacksonObjectMapperBuilder.deserializerByType(ZonedDateTime.class,
                new CustomZonedDateTimeDeserializer(PatternConstant.Date.NORM_ZONED_DATETIME_FORMATTER));
        jacksonObjectMapperBuilder.deserializerByType(OffsetDateTime.class,
                new CustomOffsetDateTimeDeserializer(PatternConstant.Date.NORM_OFFSET_DATETIME_FORMATTER));
        jacksonObjectMapperBuilder.deserializerByType(OffsetTime.class,
                new CustomOffsetTimeDeserializer(PatternConstant.Date.NORM_OFFSET_TIME_FORMATTER));

        setDateFormatByProperties(jacksonObjectMapperBuilder);
    }

    /**
     * 根据配置文件设置时间解析格式
     *
     * @param jacksonObjectMapperBuilder ObjectMapper实例构建器
     * @since 1.0.0
     */
    private void setDateFormatByProperties(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
        String defaultDatePattern = PatternConstant.Date.NORM_DATETIME_PATTERN;
        if (Objects.isNull(jacksonProperties)) {
            jacksonObjectMapperBuilder.dateFormat(new SimpleDateFormat(defaultDatePattern));
            jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class,
                    new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(defaultDatePattern)));
            jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class,
                    new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(defaultDatePattern)));
        } else {
            TimeZone timeZone = jacksonProperties.getTimeZone();
            if (Objects.isNull(timeZone)) {
                timeZone = DEFAULT_TIME_ZONE;
            }
            String dateFormat = jacksonProperties.getDateFormat();
            if (Objects.isNull(dateFormat)) {
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat(defaultDatePattern);
                simpleDateFormat.setTimeZone(timeZone);
                jacksonObjectMapperBuilder.dateFormat(simpleDateFormat);
            } else {
                try {
                    Class<?> dateFormatClass = ClassUtils.forName(dateFormat, null);
                    jacksonObjectMapperBuilder.dateFormat((DateFormat) BeanUtils.instantiateClass(dateFormatClass));
                } catch (ClassNotFoundException ex) {
                    SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormat);
                    simpleDateFormat.setTimeZone(timeZone);
                    jacksonObjectMapperBuilder.dateFormat(simpleDateFormat);
                    jacksonObjectMapperBuilder.serializerByType(LocalDateTime.class,
                            new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(dateFormat)));
                    jacksonObjectMapperBuilder.deserializerByType(LocalDateTime.class,
                            new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
                }
            }
            jacksonObjectMapperBuilder.timeZone(timeZone);
        }
    }
}
